package robocup.component.situations;

import robocup.component.Prophet;
import robocup.component.WorldModel;
import robocup.component.actions.Action;
import robocup.component.worldobjects.Player;

/**
 * The abstract <code>Situation</code> class. Things needed in all Situations
 * are defined here.<br>
 * 
 * Using situations we try to describe and classify the world on a higher level.
 * A situation is suitable only for certain worlds (i.e. the PassSituation
 * matches only worlds where we have the ball control) and there may be more
 * than one situation classifying a given world (i.e. Passing and Dribbling). If
 * a situation occurs each child instance of the <code>Situation</code> class
 * will try to find out what action fist best this situation and what benefit
 * would result from performing this action. The benefits and actions will later
 * be used by the tactic on the state-level to determine the best state. A
 * situation also creates new knowledge from the worldmodel facts we know. It's
 * calculated(updated) only once when needed!<br>
 * The evaluation of all situations is done in the {@link SituationEvaluator}.
 * <br>
 * <br>
 * 
 * The devision between states and situations is not very strict as situations
 * already perform the benefit and bestAction calculations keeping the states
 * themself pretty simple. For a better understanding you might think of a 2
 * layered approach with the Situations doing most of the resource intensive
 * calculations and the states used for choosing from a pool of situations the
 * best resulting action.
 */
public abstract class Situation {

  private static final String EMPTY_STRING   = "";

  /**
   * the {@link #subStateString} is used to inform the gui what part of the
   * situation's decision tree was chosen.
   */
  protected String      subStateString = Situation.EMPTY_STRING;

  protected WorldModel    world;

  protected Prophet       prophet;

  protected Movements     movements;

  protected Player[]      allPlayers;

  /**
   * as each situation is calcualted only once for a given world we remember the
   * last update time. as in 1 cycle the world may be update only 2x (after BSI
   * and VI arrival) each situation is at most calculated 2x a cycle (SayInfos
   * will NOT trigger a new calculation process but will be considered in the VI
   * process as they arrive previous to VIs).
   */
  private int           timestamp      = -1;

  /**
   * the benefit for this situation. 0 when the situation does not fit the
   * world.
   */
  protected double      benefit        = 0;

  /**
   * the best action resulting from the situation. "null" if the situation does
   * not fit the world.
   */
  protected Action      bestAction     = null;

  /**
   * The super-class of all situations, providing some methods that should be
   * reused in order to fit to the framework.
   * 
   * @param world -
   *          the current world-model
   * @param prophet -
   *          the parent object, holding all situations
   * @param movements -
   *          the generic movement-model
   */
  public Situation(
      final WorldModel world,
      final Prophet prophet,
      final Movements movements) {

    this.world = world;
    this.prophet = prophet;
    this.movements = movements;
    this.allPlayers = world.getAllPlayers();
  }

  /**
   * updates the situation
   * 
   */
  protected void update() {

    this.setUpdated();
  }

  /**
   * sets a timestamp. so the situation doesn't have to be recalculated if the
   * world didn't change.
   * 
   */
  protected void setUpdated() {

    this.timestamp = this.world.getBodyCycle() * 10 + (this.world.isViBased() ? 1 : 0);
  }

  /**
   * the situation was already calculated based on the given world.
   * 
   * @return true if calculated
   */
  protected boolean wasUpdated() {

    boolean ret = this.timestamp == this.world.getBodyCycle() * 10 + (this.world
        .isViBased() ? 1 : 0);

    if (!ret) {
      this.subStateString = Situation.EMPTY_STRING;
    }
    return ret;
  }

  /**
   * the situations benefit.
   * 
   * @return
   */
  public double getBenefit() {

    if (!this.wasUpdated()) {
      this.update();
    }

    return this.benefit;
  }

  /**
   * the action resulting in the best benefit.
   * 
   * @return the best action
   */
  public Action getBestAction() {

    if (!this.wasUpdated()) {
      this.update();
    }

    return this.bestAction;
  }

  /**
   * @return the subStateString showing which way in the decision tree of the
   *         Situation was chosen. (To be shown in the GUI.)
   */
  public String getSubStateString() {

    if (!this.wasUpdated()) {
      this.update();
    }

    return this.subStateString;
  }

  /**
   * sets the substate
   * 
   * @param s -
   *          the subStateString showing which way in the decision tree of the
   *          Situation was chosen. (To be shown in the GUI.)
   */
  public void setSubStateString(final String s) {

    this.subStateString = s;
  }

  /**
   * @return Returns the allPlayers.
   */
  public Player[] getAllPlayers() {

    return this.allPlayers;
  }

  /**
   * @return Returns the movements.
   */
  public Movements getMovements() {

    return this.movements;
  }

  /**
   * @return Returns the world.
   */
  public WorldModel getWorld() {

    return this.world;
  }

  /**
   * @param benefit -
   *          The benefit of the current situation when choosing the
   *          <code>{@link #bestAction}</code>.
   */
  public void setBenefit(final double benefit) {

    this.benefit = benefit;
  }

  /**
   * @param bestAction
   *          The bestAction in the current situation.
   */
  public void setBestAction(final Action bestAction) {

    this.bestAction = bestAction;
  }

  /**
   * @return Returns the prophet.
   */
  public Prophet getProphet() {

    return this.prophet;
  }

  /**
   * @return Returns the timestamp.
   */
  public int getTimestamp() {

    return this.timestamp;
  }
  
  /**
   * 
   * @return the evaluator for all situations
   */
  protected SituationEvaluator getSituationEvaluator(){
    return this.getProphet().getSituationEvaluator();
  }
}
